import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import javax.swing.*;
import javax.swing.event.*;

public class Editor
extends LMCElement {

    public static final String[] EXAMPLES = {
	"iotest.lmc",
	"asmerr.lmc",
	"rterr.lmc",
	"infinite.lmc",
	"sums1.lmc",
	"sums2.lmc",
	"selfModify.lmc"
    };

    private JTextArea textArea;
    private JScrollPane scrollPane;

    private JFileChooser fileChooser;
    private File sourceFile;
    private Action saveAction;
    private boolean textIsModified;
    private JOptionPane exampleChooser;
    private DocumentListener modificationListener;

    private StringBuffer textBuffer;
    private Program program;

    private void readText(InputStream strm)
    throws IOException {
	textBuffer.setLength(0);
	Reader reader = new InputStreamReader(strm);
	BufferedReader bufferedReader = new BufferedReader(reader);
	String str = bufferedReader.readLine();
	while (str != null) {
	    str += "\n";
	    textBuffer.append(str);
	    str = bufferedReader.readLine();
	}
	bufferedReader.close();
	textArea.setText(textBuffer.toString());
    }

    private void readText(File f) {
	try {
	    readText(new FileInputStream(f));
	    textArea.setText(textBuffer.toString());
	    textArea.setCaretPosition(0);
	    sourceFile = f;
	    saveAction.setEnabled(true);
	    LMC.getAssembler().clearStatus();
	} catch(IOException e) {
	    JOptionPane.showMessageDialog(null, e.getMessage(),
	            "File Error", JOptionPane.ERROR_MESSAGE);
	}
    }

    private void readText(URL url) {
	try {
	    readText(url.openStream());
	    textArea.setText(textBuffer.toString());
	    textArea.setCaretPosition(0);
	    sourceFile = null;
	    saveAction.setEnabled(false);
	    LMC.getAssembler().clearStatus();
	} catch(IOException e) {
	    JOptionPane.showMessageDialog(null, e.getMessage(),
	            "File Error", JOptionPane.ERROR_MESSAGE);
	}
    }

    private String getText() {
	String text = textArea.getText();
	int length = text.length();
	if (length == 0 || text.charAt(length - 1) != '\n') {
	    text = text + "\n";
	}
	return text;
    }

    private void writeText(File f) {
	try {
	    FileWriter writer = new FileWriter(f);
	    writer.write(getText());
	    writer.close();
	} catch(IOException e) {
	    JOptionPane.showMessageDialog(null, e.getMessage(),
	            "File Error", JOptionPane.ERROR_MESSAGE);
	}
    }

    private void openFile() {
	int status = fileChooser.showOpenDialog(frame);
	if (status == JFileChooser.APPROVE_OPTION) {
	    File file = fileChooser.getSelectedFile();
	    readText(file);
	}
    }

    private void openExample() {
	String selectedExample = (String)JOptionPane.showInputDialog(
	    frame,
	    "Select example",
	    "Examples",
	    JOptionPane.QUESTION_MESSAGE,
	    null,
	    EXAMPLES,
	    EXAMPLES[0]
	);
	if (selectedExample != null) {
	    readText(getClass().getResource("examples/" + selectedExample));
	}
    }

    private void saveFile() {
	int status = fileChooser.showSaveDialog(frame);
	if (status == JFileChooser.APPROVE_OPTION) {
	    File file = fileChooser.getSelectedFile();
	    writeText(file);
	}
    }

    protected Action[] createToolBarActions() {
	Action newAction = new AbstractAction() {
	    public void actionPerformed(ActionEvent e) {
		if (checkIfModified()) {
		    textArea.setText("");
		    textIsModified = false;
		    LMC.getAssembler().clearStatus();
		}
	    }
	};
	newAction.putValue(Action.NAME, "New");
	newAction.putValue(Action.SHORT_DESCRIPTION, "Edit a new program");
	Action openAction = new AbstractAction() {
	    public void actionPerformed(ActionEvent e) {
		if (checkIfModified()) {
		    openFile();
		    textIsModified = false;
		}
	    }
	};
	openAction.putValue(Action.NAME, "Open");
	openAction.putValue(Action.SHORT_DESCRIPTION, "Open a file");
	openAction.setEnabled(LMC.isApplication());
	Action examplesAction = new AbstractAction() {
	    public void actionPerformed(ActionEvent e) {
		if (checkIfModified()) {
		    openExample();
		    textIsModified = false;
		}
	    }
	};
	examplesAction.putValue(Action.NAME, "Examples");
	examplesAction.putValue(Action.SHORT_DESCRIPTION, "Open an example");
	saveAction = new AbstractAction() {
	    public void actionPerformed(ActionEvent e) {
		writeText(sourceFile);
		textIsModified = false;
	    }
	};
	saveAction.putValue(Action.NAME, "Save");
	saveAction.putValue(Action.SHORT_DESCRIPTION, "Save to source file");
	saveAction.setEnabled(false);
	Action saveAsAction = new AbstractAction() {
	    public void actionPerformed(ActionEvent e) {
		saveFile();
		textIsModified = false;
	    }
	};
	saveAsAction.putValue(Action.NAME, "Save As ...");
	saveAsAction.putValue(Action.SHORT_DESCRIPTION, "Save as a new file");
	saveAsAction.setEnabled(LMC.isApplication());
	return new Action[] {
	    newAction,
	    openAction,
	    examplesAction,
	    saveAction,
	    saveAsAction
	};
    }

    protected JComponent createVisual() {
	textArea = new JTextArea(40, 60);
        textArea.setFont(LMC.font);
	textArea.setEditable(true);
	JScrollPane scrollPane = new JScrollPane(textArea,
		JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
		JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
	return scrollPane;
    }

    public Editor() {
	super();
	frame.setTitle("LMC Editor");
	frame.setLocation(0, 0);
	frame.setIconifiable(true);
	frame.setResizable(true);
	textBuffer = new StringBuffer();
	if (LMC.isApplication()) {
	    fileChooser = new JFileChooser();
	    fileChooser.setFileFilter(new javax.swing.filechooser.FileFilter() {
		public boolean accept(File f) {
		    return f.getName().endsWith(".lmc");
		}
		public String getDescription() {
		    return "LMC assembly language files";
		}
	    });
	    String cwd = System.getProperty("user.dir");
	    if (cwd != null) {
		fileChooser.setCurrentDirectory(new File(cwd));
	    }
	} else {
	    fileChooser = null;
	}

	program = new Program();

	modificationListener = new DocumentListener() {
	    public void changedUpdate(DocumentEvent e) {
		textIsModified = true;
		LMC.getAssembler().clearStatus();
	    }
	    public void insertUpdate(DocumentEvent e) {
		textIsModified = true;
		LMC.getAssembler().clearStatus();
	    }
	    public void removeUpdate(DocumentEvent e) {
		textIsModified = true;
		LMC.getAssembler().clearStatus();
	    }
	};
	textArea.getDocument().addDocumentListener(modificationListener);

    }

    public JComponent getComponent() {
	return scrollPane;
    }

    public void selectToken(Token tkn) {
	// System.out.println("Start = " + tkn.getStart());
	// System.out.println("End   = " + tkn.getEnd());
	textArea.setCaretPosition(tkn.getStart());
	textArea.moveCaretPosition(tkn.getEnd());
    }

    public Statement getCurrentStatement() {
	int position = textArea.getCaretPosition();
	java.util.Iterator iter = program.getStatements();
	while (iter.hasNext()) {
	    Statement statement = (Statement)iter.next();
	    if (position <= statement.getEntire().getEnd()) {
		return statement;
	    }
	}
	return null;
    }

    public void parseStatements() {
	program.setText(textArea.getText());
    }

    public java.util.Iterator getStatements() {
	return program.getStatements();
    }

    public void setEditable(boolean ed) {
	textArea.setEditable(ed);
	if (ed) {
	    textArea.requestFocus();
	}
    }

    public boolean checkIfModified() {
	if (!textIsModified || !LMC.isApplication()) {
	    return true;
	}
	int response = JOptionPane.showOptionDialog(
	    frame,
	    "The program has been modified.\nDo you want to save it?",
	    "Program modified",
	    JOptionPane.YES_NO_CANCEL_OPTION,
	    JOptionPane.QUESTION_MESSAGE,
	    null,
	    null,
	    null
	);
	if (response == JOptionPane.YES_OPTION) {
	    if (sourceFile != null) {
		writeText(sourceFile);
	    } else {
		saveFile();
	    }
	}
	if (response != JOptionPane.CANCEL_OPTION) {
	    textIsModified = false;
	}
	return response != JOptionPane.CANCEL_OPTION;
    }

}  // public class Editor

